﻿using BeerScanatronClient.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity.Validation;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BeerScanatronServer
{
    public partial class ServerMain : Form
    {
        private ServiceHost _host;

        public ServerMain()
        {
            InitializeComponent();

            pgConnectionProps.PropertyValueChanged += PgConnectionProps_PropertyValueChanged;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            ServerProperties props = new ServerProperties()
            {
                DatabaseName = Properties.Settings.Default.DatabaseName,
                ServerName = Properties.Settings.Default.ServerName,
                ServerPort = Properties.Settings.Default.ServerPort,
                AutoStart = Properties.Settings.Default.AutoStart
            };
            pgConnectionProps.SelectedObject = props;
            Trace.Listeners.Add(new ListBoxListener(lbLog));
            btnStop.Enabled = false;

            if (props.AutoStart)
            {
                StartServer();
            }
        }

        private void btnCreateDb_Click(object sender, EventArgs e)
        {
            try
            {
                Cursor = Cursors.WaitCursor;
                if (TestDBConnection())
                {
                    MessageBox.Show("Database already exists!");
                    return;
                }

                using (ScanatronDbContext db = new ScanatronDbContext(GetDBConnectionString()))
                {
                    Setting setting = db.Settings.FirstOrDefault();

                    // If we don't have a setting yet, go ahead and create it
                    if (setting == null)
                    {
                        setting = new Setting();
                        setting.BeerMax = 2;
                        setting.Id = 1;

                        db.Settings.Add(setting);
                        db.SaveChanges();
                    }
                }
            }
            catch (DbEntityValidationException ex)
            {
                Trace.TraceError(ex.EntityValidationErrors.ToString());
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            StartServer();
        }

        private void StartServer()
        {
            string conString = GetDBConnectionString();
            Cursor = Cursors.WaitCursor;

            try
            {
                ServerProperties props = (ServerProperties)pgConnectionProps.SelectedObject;

                // Make sure we can connection to the database first
                if (!TestDBConnection()) return;

                // Get port from settings to allow admin to adjust if necessary.
                Uri baseAddress = new UriBuilder("http", props.ServerName, props.ServerPort, "BeerScanatron").Uri;

                // Add binding to allow maximum received size of pictures to be handled
                //  without error: (413) Request Entity Too Large
                _host = new ServiceHost(new ScanatronService(conString), baseAddress);
                BasicHttpBinding binding = new BasicHttpBinding();
                binding.MaxReceivedMessageSize = 1024 * 1024;
                _host.AddServiceEndpoint(typeof(ScanatronService), binding, baseAddress);

                // Enable metadata publishing.
                ServiceMetadataBehavior smb = new ServiceMetadataBehavior();
                smb.HttpGetEnabled = true;
                smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
                _host.Description.Behaviors.Add(smb);

                _host.Open();
                btnStop.Enabled = true;
                btnStart.Enabled = false;
                btnCreateDb.Enabled = false;
                pgConnectionProps.Enabled = false;
                Trace.WriteLine($"Server URL: {baseAddress.AbsoluteUri}");
                Trace.WriteLine($"SQL connection: {conString}");
            }
            catch (Exception ex)
            {
                Trace.TraceError(ex.Message + "\n\nYou may need to start this application with \"Run as Administrator\" or adjust the ServerPort property.",
                    "Failed to start");
            }
            finally
            {
                Cursor = Cursors.Default;
            }
        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            try
            {
                if (_host.State == CommunicationState.Opened || _host.State == CommunicationState.Opening)
                {
                    // Don't attempt to close connection if closed or faulted.  May generate exception.
                    _host?.Close();
                }

                btnStop.Enabled = false;
                btnStart.Enabled = true;
                btnCreateDb.Enabled = true;
                pgConnectionProps.Enabled = true;
                Trace.WriteLine("Server Stopped");
                Trace.WriteLine("");
            }
            catch (Exception ex)
            {
                Trace.TraceError("Failed to stop", ex.Message);
            }
        }

        private void PgConnectionProps_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
        {
            ServerProperties tempProps = new ServerProperties();
            bool resetItem = false;

            switch (e.ChangedItem.Label)
            {
                case "ServerName":
                case "DatabaseName":
                    if (string.IsNullOrEmpty((string)e.ChangedItem.Value))
                    {
                        resetItem = true;
                        MessageBox.Show($"{e.ChangedItem.Label} cannot be blank!");
                    }
                    break;

                default:
                    break;
            }

            if (resetItem)
            {
                e.ChangedItem.PropertyDescriptor.SetValue(pgConnectionProps.SelectedObject, e.OldValue);
            }
        }

        protected override void OnClosing(CancelEventArgs e)
        {
            if (_host != null && (_host.State == CommunicationState.Opened || _host.State == CommunicationState.Opening))
            {
                // Don't attempt to close connection if closed or faulted.  May generate exception.
                _host?.Close();
            }

            try
            {
                ServerProperties props = (ServerProperties)pgConnectionProps.SelectedObject;
                Properties.Settings.Default.DatabaseName = props.DatabaseName;
                Properties.Settings.Default.ServerName = props.ServerName;
                Properties.Settings.Default.ServerPort = props.ServerPort;
                Properties.Settings.Default.AutoStart = props.AutoStart;
                Properties.Settings.Default.Save();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Error updating properties: {ex.ToString()}");
            }

            base.OnClosing(e);
        }

        private bool TestDBConnection()
        {
            SqlConnection conn = null;
            string connectionString = GetDBConnectionString();
            try
            {
                using (conn = new SqlConnection(connectionString))
                {
                    conn.Open(); // throws if invalid
                }

                return true;
            }
            catch (Exception ex)
            {
                Trace.TraceError($"Unable to connect to database at: {connectionString}:{Environment.NewLine}{ex.ToString()}");
            }
            finally
            {
                if (conn != null && conn.State != ConnectionState.Closed && conn.State != ConnectionState.Broken)
                {
                    conn.Close();
                }
            }

            return false;
        }

        private string GetDBConnectionString()
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.InitialCatalog = ((ServerProperties)pgConnectionProps.SelectedObject).DatabaseName;
            builder.DataSource = ((ServerProperties)pgConnectionProps.SelectedObject).ServerName;
            builder.IntegratedSecurity = true;

            return builder.ConnectionString;
        }
    }
}